package com.zusmart.core.bean.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.zusmart.base.graph.DirectedGraph;
import com.zusmart.base.graph.DirectedGraphUtil;
import com.zusmart.base.logging.Logger;
import com.zusmart.base.logging.LoggerFactory;
import com.zusmart.base.toolkit.AntPathMatcher;
import com.zusmart.base.util.AnnotationUtils;
import com.zusmart.base.util.ReflectionUtils;
import com.zusmart.base.util.StringUtils;
import com.zusmart.core.bean.BeanDefinition;
import com.zusmart.core.bean.BeanDefinitionRegistry;
import com.zusmart.core.bean.cause.BeanDefinitionMultipleException;
import com.zusmart.core.bean.cause.BeanDefinitionNotFoundException;
import com.zusmart.core.bean.cause.BeanDefinitionReferenceCycleException;
import com.zusmart.core.bean.cause.BeanInstanceInjectException;
import com.zusmart.core.bean.cause.BeanInstanceNotFoundException;
import com.zusmart.core.bean.support.AbstractBeanDefinitionResolver;
import com.zusmart.core.bean.support.BeanDefinitionForClass;
import com.zusmart.core.bean.support.BeanDefinitionForMethod;
import com.zusmart.core.bean.support.BeanDefinitionForObject;
import com.zusmart.core.condition.ConditionManager;
import com.zusmart.core.config.ConfigData;
import com.zusmart.core.config.ConfigSourceManager;
import com.zusmart.core.plugin.PluginManager;
import com.zusmart.core.proxy.ProxyFactory;

public class AnnotationBeanDefinitionResolver extends AbstractBeanDefinitionResolver {

	private static final Logger logger = LoggerFactory.getLogger(AnnotationBeanDefinitionResolver.class);

	public AnnotationBeanDefinitionResolver(BeanDefinitionRegistry beanDefinitionRegistry) {
		super(beanDefinitionRegistry);
	}

	@Override
	public String getBeanName(Class<?> beanClass) {
		String newBeanName = StringUtils.toCamelName(beanClass.getSimpleName());
		if (AnnotationUtils.hasAnnotation(beanClass, Component.class)) {
			Component component = AnnotationUtils.getAnnotation(beanClass, Component.class);
			if (StringUtils.isNotBlank(component.value())) {
				newBeanName = component.value();
			}
		}
		return newBeanName;
	}

	@Override
	public String getBeanName(Object beanInstance) {
		return StringUtils.toCamelName(beanInstance.getClass().getSimpleName());
	}

	@Override
	public String getBeanName(Method parentBeanMethod) {
		String newBeanName = StringUtils.toCamelName(parentBeanMethod.getName());
		if (AnnotationUtils.hasAnnotation(parentBeanMethod, Component.class)) {
			Component component = AnnotationUtils.getAnnotation(parentBeanMethod, Component.class);
			if (StringUtils.isNotBlank(component.value())) {
				newBeanName = component.value();
			}
		}
		return newBeanName;
	}

	@Override
	public String getBeanScope(Class<?> beanClass) {
		String newBeanScope = BeanDefinition.SCOPE_SINGLETON;
		if (AnnotationUtils.hasAnnotation(beanClass, Component.class)) {
			Component component = AnnotationUtils.getAnnotation(beanClass, Component.class);
			if (StringUtils.isNotBlank(component.scope())) {
				newBeanScope = component.scope();
			}
		}
		return newBeanScope;
	}

	@Override
	public String getBeanScope(Method parentBeanMethod) {
		String newBeanScope = BeanDefinition.SCOPE_SINGLETON;
		if (AnnotationUtils.hasAnnotation(parentBeanMethod, Bean.class)) {
			Bean bean = AnnotationUtils.getAnnotation(parentBeanMethod, Bean.class);
			if (StringUtils.isNotBlank(bean.value())) {
				newBeanScope = bean.value();
			}
		}
		return newBeanScope;
	}

	@Override
	public void injectField(Class<?> beanType, Object beanInstance) throws BeanInstanceInjectException {
		Field[] fields = ReflectionUtils.getDeclaredFields(beanType);
		for (Field field : fields) {
			if (AnnotationUtils.hasAnnotation(field, Autowired.class)) {
				Autowired autowired = AnnotationUtils.getAnnotation(field, Autowired.class);
				String beanName = autowired.value();
				try {
					BeanDefinition beanDefinition = this.getBeanDefinitionRegistry().getBeanDefinition(beanName, field.getType());
					Object injectInstance = beanDefinition.getBeanInstance();
					ReflectionUtils.makeAccessible(field);
					field.set(beanInstance, injectInstance);
				} catch (BeanDefinitionNotFoundException e) {
					String msg = String.format("bean definition not found %s#%s", field.getType().getName(), beanName);
					logger.error(msg, e);
					throw new BeanInstanceInjectException(msg, e);
				} catch (Exception e) {
					String msg = String.format("inject bean field error %s.%s", field.getType().getName(), field.getName());
					logger.error(msg, e);
					throw new BeanInstanceInjectException(msg, e);
				}
			}
			if (AnnotationUtils.hasAnnotation(field, Property.class)) {
				Property property = AnnotationUtils.getAnnotation(field, Property.class);
				if (!StringUtils.isBlank(property.value())) {
					String prefix = "";
					if (AnnotationUtils.hasAnnotation(beanInstance.getClass(), Property.class)) {
						Property classProperty = AnnotationUtils.getAnnotation(beanInstance.getClass(), Property.class);
						if (!StringUtils.isBlank(classProperty.prefix())) {
							prefix = classProperty.prefix();
						}
					}
					if (!StringUtils.isBlank(property.prefix())) {
						if (StringUtils.isBlank(prefix)) {
							prefix = property.prefix();
						} else {
							if (prefix.endsWith(".")) {
								prefix += property.prefix();
							} else {
								prefix += "." + property.prefix();
							}
						}
					}
					try {
						String configName = String.format("%s(%s)", property.value(), prefix);
						ConfigData<?> configData = this.getBeanDefinitionRegistry().getConfigSourceManager().getConfigData(field.getType(), configName);
						if(!configData.isNotFound()){
							ReflectionUtils.makeAccessible(field);
							field.set(beanInstance, configData.getData());
						}
					} catch (Exception e) {
						String msg = String.format("inject bean field error %s.%s", field.getType().getName(), field.getName());
						logger.error(msg, e);
						throw new BeanInstanceInjectException(msg, e);
					}
				}
			}
		}
	}

	@Override
	public void inejctMethod(Class<?> beanType, Object beanInstance) throws BeanInstanceInjectException {
		Method[] methods = ReflectionUtils.getDeclaredMethods(beanType);
		for (Method method : methods) {
			if (AnnotationUtils.hasAnnotation(method, Autowired.class) || AnnotationUtils.hasAnnotation(method.getParameterAnnotations(), Autowired.class) || AnnotationUtils.hasAnnotation(method, Property.class) || AnnotationUtils.hasAnnotation(method.getParameterAnnotations(), Property.class)) {
				try {
					Object[] parameterDatas = this.getBeanArguments(method);
					ReflectionUtils.makeAccessible(method);
					ReflectionUtils.invokeMethod(method, beanInstance, parameterDatas);
				} catch (BeanDefinitionNotFoundException e) {
					String msg = String.format("inject bean field error %s.%s", beanInstance.getClass().getName(), method.getName());
					logger.error(msg, e);
					throw new BeanInstanceInjectException(msg, e);
				}
			}
		}
	}

	@Override
	public Set<BeanDefinition> getSortedBeanDefinitions() throws BeanDefinitionReferenceCycleException, BeanDefinitionNotFoundException, BeanDefinitionMultipleException {
		ConfigSourceManager configSourceManager = this.getBeanDefinitionRegistry().getConfigSourceManager();
		ConditionManager conditionManager = this.getBeanDefinitionRegistry().getConditionManager();
		PluginManager pluginManager = this.getBeanDefinitionRegistry().getPluginManager();
		Map<String, BeanDefinition> beanDefinitions = this.getBeanDefinitionRegistry().getBeanDefinitionMappings();
		Map<String, BeanDefinition> resolveBeanDefinitions = new LinkedHashMap<String, BeanDefinition>();
		for (Entry<String, BeanDefinition> entry : beanDefinitions.entrySet()) {
			conditionManager.attachBeanDefinition(entry.getValue());
		}
		for (Entry<String, BeanDefinition> entry : beanDefinitions.entrySet()) {
			if (conditionManager.execute(entry.getValue(), pluginManager, configSourceManager)) {
				resolveBeanDefinitions.put(entry.getKey(), entry.getValue());
			}
		}
		DirectedGraph<BeanDefinition> directedGraph = new DirectedGraph<BeanDefinition>();
		DirectedGraphUtil<BeanDefinition> directedGraphUtil = new DirectedGraphUtil<BeanDefinition>(directedGraph);
		for (Entry<String, BeanDefinition> entry : resolveBeanDefinitions.entrySet()) {
			this.resolveBeanReference(entry.getValue(), directedGraph);
		}
		if (directedGraphUtil.hasCycle()) {
			throw new BeanDefinitionReferenceCycleException();
		}
		return directedGraphUtil.getSort();
	}

	@Override
	public Set<BeanDefinition> getBeanDefinitionProxy(Class<?> beanType) throws BeanDefinitionNotFoundException {
		Set<BeanDefinition> beanDefinitions = new LinkedHashSet<BeanDefinition>();
		for (Entry<String, BeanDefinition> entry : this.getBeanDefinitionRegistry().getBeanDefinitionMappings().entrySet()) {
			BeanDefinition beanDefinition = entry.getValue();
			if (this.isTargetInterceptor(beanType, beanDefinition)) {
				beanDefinitions.add(beanDefinition);
			}
		}
		return beanDefinitions;
	}

	@Override
	protected int doOrderMethod(Method methodA, Method methodB) {
		int sortA = Integer.MAX_VALUE;
		int sortB = Integer.MAX_VALUE;
		Order orderA = AnnotationUtils.getAnnotation(methodA, Order.class);
		Order orderB = AnnotationUtils.getAnnotation(methodB, Order.class);
		if (null != orderA) {
			sortA = orderA.value();
		}
		if (null != orderB) {
			sortB = orderB.value();
		}
		return sortA - sortB;
	}

	@Override
	protected boolean isInitialMethod(Method method) {
		return AnnotationUtils.hasAnnotation(method, Initial.class);
	}

	@Override
	protected boolean isDestoryMethod(Method method) {
		return AnnotationUtils.hasAnnotation(method, Destory.class);
	}

	@Override
	protected Constructor<?> doGetBeanConstructor(Constructor<?>[] constructors) {
		Constructor<?> noArgumentsConstructor = null;
		List<Constructor<?>> resolveConstructors = new ArrayList<Constructor<?>>();
		for (int i = 0; i < constructors.length; i++) {// 搜索无参构造函数
			Constructor<?> tempConstructor = constructors[i];
			if (tempConstructor.getParameterTypes() == null || tempConstructor.getParameterTypes().length == 0) {
				noArgumentsConstructor = tempConstructor;
				break;
			}
		}
		for (int i = 0; i < constructors.length; i++) {// 搜索含有@Initial注解的构造函数
			Constructor<?> tempConstructor = constructors[i];
			if (AnnotationUtils.hasAnnotation(tempConstructor, Autowired.class)) {
				resolveConstructors.add(tempConstructor);
			}
		}
		if (resolveConstructors.size() == 0) {// 如果没有@Initial注解的构造函数
			if (null == noArgumentsConstructor) {// 且没有无参构造函数,返回构造函数数组中的第一个
				return constructors[0];
			} else {
				return noArgumentsConstructor;// 返回无参构造函数
			}
		} else if (resolveConstructors.size() == 1) {// 如果只搜索到一个含有@Initial注解的构造函数,则使用含有@Initial注解的构造函数
			return resolveConstructors.get(0);
		} else {// 如果搜索到多个@Initial注解的构造函数,默认返回参数中含有@Autowired注解的第一个构造函数
			for (Constructor<?> tempConstructor : resolveConstructors) {
				if (AnnotationUtils.hasAnnotation(tempConstructor.getParameterAnnotations(), Autowired.class)) {
					return tempConstructor;
				}
			}
			return resolveConstructors.get(0);// 如果参数都没有含有@Autowired注解的,则返回第一个含有@Initial注解的构造函数
		}
	}

	@Override
	protected Object[] doGetBeanArguments(Class<?> declaringClass, AccessibleObject accessibleObject, Class<?>[] parameterTypes, Annotation[][] annotations) throws BeanDefinitionNotFoundException {
		if (parameterTypes.length == 0) {
			return new Object[0];
		}
		Object[] result = new Object[parameterTypes.length];
		Autowired accessibleAutowired = AnnotationUtils.getAnnotation(accessibleObject, Autowired.class);
		Property accessibleProperty = AnnotationUtils.getAnnotation(accessibleObject, Property.class);
		Property classProperty = AnnotationUtils.getAnnotation(declaringClass, Property.class);
		String propertyPrefix = "";
		if (null != classProperty && StringUtils.isNotBlank(classProperty.prefix())) {
			propertyPrefix += classProperty.prefix();
			if (propertyPrefix.startsWith(".")) {
				propertyPrefix = propertyPrefix.substring(1);
			}
			if (!propertyPrefix.endsWith(".")) {
				propertyPrefix += ".";
			}
		}
		if (null != accessibleProperty && StringUtils.isNotBlank(accessibleProperty.prefix())) {
			String prefix = accessibleProperty.prefix();
			if (prefix.startsWith(".")) {
				prefix = prefix.substring(1);
			}
			propertyPrefix += prefix;
			if (!propertyPrefix.endsWith(".")) {
				propertyPrefix += ".";
			}
		}
		ConfigSourceManager configSourceManager = this.getBeanDefinitionRegistry().getConfigSourceManager();
		Annotation[] autowireds = AnnotationUtils.getAnnotation(parameterTypes, annotations, Autowired.class);
		Annotation[] properties = AnnotationUtils.getAnnotation(parameterTypes, annotations, Property.class);
		for (int i = 0; i < parameterTypes.length; i++) {
			Autowired autowired = (Autowired) autowireds[i];
			Property property = (Property) properties[i];
			if (null == autowired && null == property) {
				result[i] = null;
				continue;
			}
			if (null != autowired && null == property) {
				// 参数含有autowired注解,不含有property注解
				result[i] = this.searchBeanInstance(i, parameterTypes, accessibleAutowired, autowired);
			} else if (null == autowired && null != property) {
				// 参数不含有autowired注解,含有property注解
				result[i] = this.searchDataInstance(i, parameterTypes, propertyPrefix, property, configSourceManager);
			} else {
				// 如果同时存在 autowited注解和 property注解,则先判断class是否被configSourceManager支持,如果不支持.查找beanDefinition注解内容.否则为null
				if (configSourceManager.isSupportConfigDataType(parameterTypes[i])) {
					result[i] = this.searchDataInstance(i, parameterTypes, propertyPrefix, property, configSourceManager);
				}
				if (null == result[i] && (!parameterTypes[i].isPrimitive())) {
					result[i] = this.searchBeanInstance(i, parameterTypes, accessibleAutowired, autowired);
				}
			}
		}
		return result;
	}

	protected void resolveBeanReference(BeanDefinition beanDefinition, DirectedGraph<BeanDefinition> directedGraph) throws BeanDefinitionNotFoundException, BeanDefinitionMultipleException {
		directedGraph.addNode(beanDefinition);
		if (beanDefinition instanceof BeanDefinitionForClass) {
			Class<?> beanType = beanDefinition.getBeanClass();
			Constructor<?> beanConstructor = this.getBeanConstructor(beanType);
			Class<?>[] parameterTypes = beanConstructor.getParameterTypes();
			if (null != parameterTypes && parameterTypes.length > 0) {
				Annotation[] annotations = AnnotationUtils.getAnnotation(parameterTypes, beanConstructor.getParameterAnnotations(), Autowired.class);
				for (int i = 0; i < annotations.length; i++) {
					if (null == annotations[i]) {
						continue;
					}
					Class<?> parameterType = parameterTypes[i];
					Autowired autowired = (Autowired) annotations[i];
					String beanName = autowired.value();
					BeanDefinition dependencyBeanDefinition = this.getBeanDefinitionRegistry().getBeanDefinition(beanName, parameterType);
					directedGraph.addEdge(beanDefinition, dependencyBeanDefinition);// 分析出构造函数需要的用的Bean依赖
				}
			}
		} else if (beanDefinition instanceof BeanDefinitionForMethod) {
			BeanDefinitionForMethod beanDefinitionForMethod = (BeanDefinitionForMethod) beanDefinition;
			BeanDefinition parentBeanDefinition = beanDefinitionForMethod.getParentBeanDefinition();
			Method createBeanMethod = beanDefinitionForMethod.getParentBeanMethod();
			directedGraph.addEdge(beanDefinition, parentBeanDefinition);
			Class<?>[] parameterTypes = createBeanMethod.getParameterTypes();
			if (null != parameterTypes && parameterTypes.length > 0) {
				Annotation[] annotations = AnnotationUtils.getAnnotation(parameterTypes, createBeanMethod.getParameterAnnotations(), Autowired.class);
				for (int i = 0; i < annotations.length; i++) {
					if (null == annotations[i]) {
						continue;
					}
					Class<?> parameterType = parameterTypes[i];
					Autowired autowired = (Autowired) annotations[i];
					String beanName = autowired.value();
					BeanDefinition dependencyBeanDefinition = this.getBeanDefinitionRegistry().getBeanDefinition(beanName, parameterType);
					directedGraph.addEdge(beanDefinition, dependencyBeanDefinition);// 分析出方法需要的用的Bean依赖
				}
			}
		} else if (beanDefinition instanceof BeanDefinitionForObject) {
			// Object 无需分析引用关系
		}
	}

	protected boolean isTargetInterceptor(Class<?> targetClass, BeanDefinition sourceBeanDefinition) {
		Class<?> beanType = sourceBeanDefinition.getBeanClass();
		if (beanType.isAssignableFrom(targetClass)) {
			return false;
		}
		if (!AnnotationUtils.hasAnnotation(beanType, Interceptor.class)) {
			return false;
		}
		if (!sourceBeanDefinition.isAssignable(ProxyFactory.class)) {
			return false;
		}
		Interceptor interceptor = AnnotationUtils.getAnnotation(beanType, Interceptor.class);
		if (null != interceptor.annotations() && interceptor.annotations().length > 0) {
			for (Class<? extends Annotation> annotation : interceptor.annotations()) {
				if (AnnotationUtils.hasAnnotation(targetClass, annotation)) {
					return true;
				}
			}
		}
		if (null != interceptor.classes() && interceptor.classes().length > 0) {
			for (Class<?> clazz : interceptor.classes()) {
				if (clazz.isAssignableFrom(targetClass)) {
					return true;
				}
			}
		}
		AntPathMatcher pathMatcher = new AntPathMatcher(".");
		if (null != interceptor.value() && interceptor.value().length > 0) {
			for (String pattern : interceptor.value()) {
				if (StringUtils.isBlank(pattern)) {
					continue;
				}
				if (pathMatcher.isPattern(pattern)) {
					if (pathMatcher.match(pattern, targetClass.getName())) {
						return true;
					}
				} else {
					if (pattern.equals(targetClass.getName())) {
						return true;
					}
				}
			}
		}
		return false;
	}

	protected Object searchDataInstance(int index, Class<?>[] parameterTypes, String propertyPrefix, Property property, ConfigSourceManager configSourceManager) throws BeanDefinitionNotFoundException {
		String propertyName = propertyPrefix;
		if (StringUtils.isNotBlank(property.prefix())) {
			String prefix = property.prefix();
			if (prefix.startsWith(".")) {
				prefix = prefix.substring(1);
			}
			propertyName += prefix;
			if (!propertyName.endsWith(".")) {
				propertyName += ".";
			}
		}
		if (StringUtils.isBlank(property.value())) {
			throw new BeanDefinitionNotFoundException(String.format("search arguments failed , because bean property value is blank (%s#%s)", parameterTypes[index].getName(), propertyName));
		}
		propertyName += property.value();
		return configSourceManager.getConfigData(parameterTypes[index], propertyName).getData();
	}

	protected Object searchBeanInstance(int index, Class<?>[] parameterTypes, Autowired accessibleAutowired, Autowired autowired) throws BeanDefinitionNotFoundException {
		String beanName = null;
		if (null != accessibleAutowired && parameterTypes.length == 1 && StringUtils.isNotBlank(accessibleAutowired.value())) {
			beanName = accessibleAutowired.value();
		}
		if (StringUtils.isNotBlank(autowired.value())) {
			beanName = autowired.value();
		}
		try {
			BeanDefinition beanDefinition = this.getBeanDefinitionRegistry().getBeanDefinition(beanName, parameterTypes[index]);
			return beanDefinition.getBeanInstance();
		} catch (BeanDefinitionNotFoundException e) {
			throw new BeanDefinitionNotFoundException(String.format("search arguments failed , because bean definition not found (%s#%s)", parameterTypes[index].getName(), beanName));
		} catch (BeanInstanceNotFoundException e) {
			throw new BeanDefinitionNotFoundException(String.format("search arguments failed , because bean instance not found (%s#%s)", parameterTypes[index].getName(), beanName));
		} catch (BeanDefinitionMultipleException e) {
			throw new BeanDefinitionNotFoundException(String.format("search arguments failed , because bean instance not found (%s#%s)", parameterTypes[index].getName(), beanName));
		}
	}

}